package websocket.stomp.config;

import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration;

import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * WebSocket配置类
 * 
 * @author zhenglian
 * @date 2019/09/23
 */
@Slf4j
//@Configuration
@AllArgsConstructor
//@EnableConfigurationProperties(StompProperties.class)
//@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    private final StompProperties stompProperties;

    /**
     * 将"/ws"路径注册为STOMP端点，这个路径与发送和接收消息的目的路径有所不同，这是一个端点， 客户端在订阅或发布消息到目的地址前，要连接该端点 即用户发送请求url="/applicationName/ws"与STOMP
     * server进行连接。之后再转发到订阅url； PS：端点的作用——客户端在订阅或发布消息到目的地址前，要连接该端点
     */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 在网页上可以通过"/applicationName/ws"来和服务器的WebSocket连接
        registry.addEndpoint("/ws")
            //                .setHandshakeHandler() // 握手处理， 主要是握手的时候认证获取其他验证信息等
            //                .addInterceptors() // 添加请求拦截
            // 方法表示允许连接的域名
            .setAllowedOrigins("*")
            // 表示支持以SockJS方式连接服务器。
            .withSockJS();
    }

    /**
     * 注册相关服务
     * @param registry
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 客户端发送消息的请求前缀, 没有特殊意义，例如：@MessageMapping("/hello")，其实真实路径是/app/hello
        registry.setApplicationDestinationPrefixes("/ws")
            // 客户端订阅消息的请求前缀，topic一般用于广播推送，queue用于点对点推送
            .enableStompBrokerRelay("/topic", "/queue")
            // ip地址
            .setRelayHost(stompProperties.getServer())
            // stomp端口
            .setRelayPort(stompProperties.getPort())
            // 用户名
            .setClientLogin(stompProperties.getUsername())
            // 密码
            .setClientPasscode(stompProperties.getPassword()).setSystemLogin(stompProperties.getUsername()).setSystemPasscode(stompProperties.getPassword()).setVirtualHost("/")
            .setUserDestinationBroadcast("/topic/unresolved-user-destination").setUserRegistryBroadcast("/topic/simp-user-registry")
            // 设置心跳信息接收时间间隔
            .setSystemHeartbeatReceiveInterval(1000)
            // 设置心跳信息发送时间间隔
            .setSystemHeartbeatSendInterval(1000);
        // 服务端通知客户端的前缀，可以不设置，默认为user
        registry.setUserDestinationPrefix("/user/");
    }

    /**
     * 配置发送与接收的消息参数，可以指定消息字节大小，缓存大小，发送超时时间
     * 
     * @param registration
     */
    @Override
    public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
        registration.setMessageSizeLimit(10240)// 设置消息缓存的字节数大小 字节
            .setSendBufferSizeLimit(10240) // 设置websocket会话时，缓存的大小 字节
            .setSendTimeLimit(10000); // 设置消息发送会话超时时间，毫秒
    }

    /**
     * 设置输入消息通道的线程数，默认线程为1，可以自己自定义线程数，最大线程数，线程存活时间
     * 
     * @param registration
     */
    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        // 配置消息线程池
        registration.taskExecutor().corePoolSize(10) // 配置核心线程池，当线程数小于此配置时，不管线程中有无空闲的线程，都会产生新线程处理任务
            .maxPoolSize(20) // 配置线程池最大数，当线程池数等于此配置时，不会产生新线程
            .keepAliveSeconds(60); // 线程池维护线程所允许的空闲时间，单位秒
        registration.interceptors(new ChannelInterceptor() {

            // 在消息发送之前调用，方法中可以对消息进行修改，如果此方法返回值为空，则不会发生实际的消息发送调用
            @Override
            public Message<?> preSend(Message<?> message, MessageChannel channel) {
                StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
                // 判断是否首次连接请求
                if (StompCommand.CONNECT.equals(accessor.getCommand())) {
                    String tokens = accessor.getFirstNativeHeader("Authorization");
                    log.info("webSocket token is {}", tokens);
                    if (StrUtil.isBlank(tokens)) {
                        return null;
                    }
                    // 验证令牌信息

                    return message;

                }
                // 不是首次连接，已经成功登陆
                return message;
            }
        });
    }

    /**
     * 设置输出消息通道的线程数，默认线程为1，可以自己自定义线程数，最大线程数，线程存活时间
     * 
     * @param registration
     */
    @Override
    public void configureClientOutboundChannel(ChannelRegistration registration) {
        registration.taskExecutor().corePoolSize(10).maxPoolSize(20).keepAliveSeconds(60);
    }

}
